package com.xyh.concurrency.example.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.StampedLock;

/**
 * juc 中的锁
 * ReentrantReadWriteLock 这个锁为读写锁当有读的线程正在进行,写的操作就会一直等待
 */
@Slf4j
public class LockExample {
    class Point {
        private double x, y;
        private final StampedLock sl = new StampedLock();

        /**
         * 改变当前坐标。
         * 先获取写锁，然后对point坐标进行修改，最后释放锁。
         * 该锁是排它锁，这保证了其他线程调用move函数时候会被阻塞，直到当前线程显示释放了该锁。
         */
        void move(double deltaX, double deltaY) { // an exclusively locked method
            long stamp = sl.writeLock();
            try {
                x += deltaX;
                y += deltaY;
            } finally {
                sl.unlockWrite(stamp);
            }
        }

        /**
         * 计算当前坐标到原点的距离
         *
         * @return
         */
        double distanceFromOrigin() { // A read-only method
            //1.尝试获取乐观读锁，返回stamp
            long stamp = sl.tryOptimisticRead();
            //2.拷贝参数到本地方法栈中
            double currentX = x, currentY = y;
            //3.验证stamp是否有效
            if (!sl.validate(stamp)) {
                //4.如果stamp无效，说明得到stamp后，又有其他线程获得了写锁
                //5.获取读锁
                stamp = sl.readLock();
                try {
                    //6.其他线程修改了x,y的值，为了数据的一致性，需要再次再次拷贝参数到本地方法栈中
                    currentX = x;
                    currentY = y;
                } finally {
                    //7.释放读锁
                    sl.unlockRead(stamp);
                }
            }
            //8.使用参数的拷贝来计算当前坐标到原点的距离。无论步骤3中stamp有没有验证成功，参数的拷贝都是当前坐标的值
            return Math.sqrt(currentX * currentX + currentY * currentY);
        }

        /**
         * 如果当前坐标为原点则移动到指定的位置
         */
        void moveIfAtOrigin(double newX, double newY) { // upgrade
            // 获取读锁，保证其他线程不能获取到写锁
            long stamp = sl.readLock();
            try {
                //如果当前坐标为原点
                while (x == 0.0 && y == 0.0) {
                    //尝试升级成写锁
                    long ws = sl.tryConvertToWriteLock(stamp);
                    //如果升级成功，更新坐标值
                    if (ws != 0L) {
                        stamp = ws;
                        x = newX;
                        y = newY;
                        break;
                    } else {//如果升级成功
                        sl.unlockRead(stamp);//先释放读锁
                        stamp = sl.writeLock();//再获取写锁
                        //循环while中的操作，直到成功更新坐标值
                    }
                }
            } finally {
                //最后释放写锁
                sl.unlock(stamp);
            }
        }
    }
}
